package service

import (
	"database/sql"
	"dbagent/pkg/util"
	"dbagent/version"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/go-kratos/kratos/pkg/log"
	_ "github.com/go-sql-driver/mysql"
	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/mem"
	"github.com/tidwall/gjson"
	//"reflect"
	//        "github.com/thinkeridea/go-extend/exnet"
)

const (
	username = "tal_dba"
	password = "Mykh1885uHhXgKFc+"
	network  = "tcp"
	server   = "127.0.0.1"
	//port = 2100
	database = "information_schema"
)

type Dbagent struct {
}

type Tables struct {
	table_schema string
	table_name   string
}

type ClustersInfo struct {
	cluster_id    int
	business_id   int
	division_id   int
	cluster_db_id int
	db_name       string
	tb_name       string
}

func (agent *Dbagent) GetDBLists(dsn string, port int) {
	var (
		cluster_db_id int
		db_name       string
		tb_name       string
		cluster_id    int
		business_id   int
		division_id   string
	)
	ip, err := agent.externalIP()
	if err != nil {
		log.Error(err.Error())
	}
	db := agent.InitDB(port, server)
	rows, err := db.Query("select table_schema as db_name,table_name as tb_name from information_schema.tables where table_schema not in ('information_schema','sys','performance_schema','test','mysql') ")
	if err != nil {
		log.Info("fetech data failed:", err.Error())
		return
	}
	zeus_db := agent.InitDB_DSN(dsn)
	clusterid_err := zeus_db.QueryRow("select a.id as cluster_id,a.business_id,a.division_id from cluster a,cluster_instance b where a.id = b.cluster_id and b.host = ? and b.port = ? ", ip.String(), port).Scan(&cluster_id, &business_id, &division_id)
	if clusterid_err != nil {
		log.Error(clusterid_err.Error())
	}
	defer rows.Close()
	for rows.Next() {
		err = rows.Scan(&db_name, &tb_name)
		db_sql := fmt.Sprintf("insert into cluster_db (cluster_id,business_id,division_id,db_name,status) values ('%d','%d','%s','%s',1) on duplicate key update status = 1", cluster_id, business_id, division_id, db_name)
		agent.insertExec(db_sql, zeus_db)
		dbid_err := zeus_db.QueryRow("select id from infra_zeus.cluster_db where db_name=? and cluster_id = ?", db_name, cluster_id).Scan(&cluster_db_id)
		if dbid_err != nil {
			log.Error(dbid_err.Error())
		}
		tb_sql := fmt.Sprintf("insert into db_table (cluster_db_id,business_id,table_name,status) values ('%d','%d','%s',1) on duplicate key update status = 0;", cluster_db_id, business_id, tb_name)
		agent.insertExec(tb_sql, zeus_db)
		checkErr(err)
	}
	err = rows.Err()
	checkErr(err)
}

func checkErr(err error) {
	if err != nil {
		log.Info(err.Error())
	}
}

func (agent *Dbagent) insertExec(sql string, db *sql.DB) {
	_, insertErr := db.Exec(sql)
	if insertErr != nil {
		log.Info("insertError", insertErr.Error())
	}

}

func (agent *Dbagent) RunV2(url string, locAddr string, startupTime time.Time, interval int) {
	tickerDuration := time.Second * time.Duration(interval)
	ticker := time.NewTicker(tickerDuration)
	for {
		select {
		case <-ticker.C:
			ttl := int64(time.Now().Sub(startupTime).Seconds())
			agent.Report(url, locAddr, ttl)
		}
	}
}

func (agent *Dbagent) externalIP() (net.IP, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return nil, err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			ip := getIpFromAddr(addr)
			if ip == nil {
				continue
			}
			return ip, nil
		}
	}
	return nil, errors.New("connected to the network?")
}

func getIpFromAddr(addr net.Addr) net.IP {
	var ip net.IP
	switch v := addr.(type) {
	case *net.IPNet:
		ip = v.IP
	case *net.IPAddr:
		ip = v.IP
	}
	if ip == nil || ip.IsLoopback() {
		return nil
	}
	ip = ip.To4()
	if ip == nil {
		return nil // not an ipv4 address
	}
	return ip
}

func IsNum(s string) bool {
	_, err := strconv.ParseFloat(s, 64)
	return err == nil
}

func (agent *Dbagent) GetPort(dsn string) int {
	cmd := exec.Command("/bin/bash", "-c", "ps -ef |grep -i mysqld|awk -F '--port=' '{print $2}'|awk '{gsub(/^\\s+|\\s+$/,\"\");print}'|awk '!/^$/'")
	std_out, _ := cmd.StdoutPipe()
	defer std_out.Close()
	if err := cmd.Start(); err != nil {
		log.Error(err.Error())
	}
	result, _ := ioutil.ReadAll(std_out)
	resdata := string(result)
	var port int
	var err error
	for i := 0; i < len(strings.Split(resdata, "\n")); i++ {
		isNum := IsNum(strings.Split(resdata, "\n")[i])
		if isNum == true {
			port, err = strconv.Atoi(strings.Split(resdata, "\n")[i])
			if err != nil {
				log.Error(err.Error())
			}
			agent.GetDBLists(dsn, port)
		} else {
			continue
		}
	}
	return port
}

func (agent *Dbagent) InitDB(port int, server string) *sql.DB {
	dsn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s", username, password, network, server, port, database)
	DB, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Error("open mysql failed,err:%v\n", err.Error())
		return nil
	}
	DB.SetConnMaxLifetime(100 * time.Second)
	DB.SetMaxOpenConns(100)
	DB.SetMaxIdleConns(16)
	return DB
}

func (agent *Dbagent) InitDB_DSN(dsn string) *sql.DB {
	DB, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Error("open mysql failed,err:%v\n", err.Error())
		return nil
	}
	DB.SetConnMaxLifetime(100 * time.Second)
	DB.SetMaxOpenConns(100)
	DB.SetMaxIdleConns(16)
	return DB
}

func (agent *Dbagent) GetDB(dsn string) {
	tickerDuration := time.Second * time.Duration(60)
	ticker := time.NewTicker(tickerDuration)
	for {
		select {
		case <-ticker.C:
			//ttl := int64(time.Now().Sub(time.Now()).Seconds())
			agent.GetPort(dsn)
			// 	agent.GetDBLists(dsn,port)
		}
	}
}

func (agent *Dbagent) Report(remoteUrl string, locAddr string, ttl int64) {
	address, _ := util.ResolveAddr("tcp4", locAddr)
	addr := strings.Split(address, ":")
	host, _ := os.Hostname()
	cpuNum, _ := cpu.Counts(true)
	memInfo, _ := mem.VirtualMemory()

	params := "host=" + host + "&ip=" + addr[0] + "&cpu=" + strconv.Itoa(cpuNum) + "&mem=" + strconv.FormatUint(memInfo.Total, 10) + "&version=" + version.VERSION + "&ttl=" + strconv.FormatInt(ttl, 10)

	json, err := util.HttpPost(remoteUrl, params)
	if err != nil {
		log.Error(err.Error())
	}
	errcode := gjson.Get(json, "errcode").Int()

	if errcode >= 300 {
		log.Error(gjson.Get(json, "errmsg").String())
	}
}
